home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Mac Format 1995 June
/
MacFormat 25.iso
/
Shareware City
/
Developers
/
OutOfPhase1.1 Source
/
OutOfPhase Folder
/
PlayAIFFFile.c
< prev
next >
Wrap
Text File
|
1995-01-04
|
28KB
|
789 lines
/* PlayAIFFFile.c */
/*****************************************************************************/
/* */
/* Out Of Phase: Digital Music Synthesis on General Purpose Computers */
/* Copyright (C) 1994 Thomas R. Lawrence */
/* */
/* This program is free software; you can redistribute it and/or modify */
/* it under the terms of the GNU General Public License as published by */
/* the Free Software Foundation; either version 2 of the License, or */
/* (at your option) any later version. */
/* */
/* This program is distributed in the hope that it will be useful, */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
/* GNU General Public License for more details. */
/* */
/* You should have received a copy of the GNU General Public License */
/* along with this program; if not, write to the Free Software */
/* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
/* */
/* Thomas R. Lawrence can be reached at tomlaw@world.std.com. */
/* */
/*****************************************************************************/
#include "MiscInfo.h"
#include "Audit.h"
#include "Debug.h"
#include "Definitions.h"
#include "PlayAIFFFile.h"
#include "BufferedFileInput.h"
#include "Files.h"
#include "Alert.h"
#include "SampleConsts.h"
#include "SoundOutput.h"
#include "DataMunging.h"
#include "EventLoop.h"
#include "Memory.h"
#include "SynthProgressWindow.h"
#define FRAMESPERBUFFER (8192L)
#define NUMBEROFBUFFERS (16)
#define INITIALNUMBUFFERS (3)
/* AIFF/AIFF-C File Format: */
/* "FORM" */
/* 4-byte big endian form chunk length descriptor (minus 8 for "FORM" & this) */
/* 4-byte type */
/* "AIFF" = AIFF format file */
/* "AIFC" = AIFF-C format file */
/* in any order, these chunks can occur: */
/* Version Chunk (this only occurs in AIFF-C files) */
/* "FVER" */
/* 4-byte big endian length, which should always be the value 4 (four) */
/* 4-byte date code. this is probably 0xA2805140 (stored big endian), but it */
/* probably doesn't matter. */
/* Common Chunk for AIFF files */
/* "COMM" */
/* 4-byte big endian length. */
/* always 18 for AIFF files */
/* 2-byte big endian number of channels */
/* 4-byte big endian number of sample frames */
/* 2-byte big endian number of bits per sample */
/* a value in the domain 1..32 */
/* 10-byte extended precision number of frames per second */
/* Common Chunk for AIFF-C files */
/* "COMM" */
/* 4-byte big endian length. */
/* 22 + compression method string length for AIFF-C files */
/* 2-byte big endian number of channels */
/* 4-byte big endian number of sample frames */
/* 2-byte big endian number of bits per sample */
/* a value in the domain 1..32 */
/* 10-byte extended precision number of frames per second */
/* 4-byte character code ID for the compression method */
/* "NONE" means there is no compression method used */
/* some characters in a string identifying the compression method */
/* this must be padded to an even number of bytes, but the pad is */
/* NOT included in the length descriptor for the chunk. */
/* for uncompressed data, the string should be */
/* "\x0enot compressed\x00", including the null, for 16 bytes. */
/* the total chunk length is thus 38 bytes. */
/* Sound Data Chunk */
/* "SSND" */
/* 4-byte big endian number of bytes in sample data array */
/* 4-byte big endian offset to the first byte of sample data in the array */
/* 4-byte big endian number of bytes to which the sound data is aligned. */
/* any length vector of raw sound data. */
/* this must be padded to an even number of bytes, but the pad is */
/* NOT included in the length descriptor for the chunk. */
/* Samples are stored in an integral number of bytes, the smallest that */
/* is required for the specified number of bits. If this is not an even */
/* multiple of 8, then the data is shifted left and the low bits are zeroed */
/* Multichannel sound is interleaved with the left channel first. */
static void PlayTheFile(BufferedInputRec* File, SynthWinRec* Window)
{
char CharBuff[4];
MyBoolean IsAnAIFFCFile;
long FormChunkLength;
long NumBits;
MyBoolean NumBitsIsValid = False;
NumChannelsType NumChannels;
MyBoolean NumChannelsIsValid = False;
long SamplingRate;
MyBoolean SamplingRateIsValid = False;
unsigned long NumSampleFrames;
MyBoolean NumSampleFramesIsValid = False;
MyBoolean DiskErrorOccurred;
/* "FORM" */
if (!ReadBufferedInput(File,4,CharBuff))
{
DiskErrorPoint1:
AlertHalt("Unable to read data from the file.",NIL);
ExitTheProcedureErrorPoint:
return;
}
if (!MemEqu(CharBuff,"FORM",4))
{
BadFilePoint1:
AlertHalt("The file is not an AIFF or AIFF-C file.",NIL);
goto ExitTheProcedureErrorPoint;
}
/* 4-byte big endian form chunk length descriptor (minus 8 for "FORM" & this) */
if (!ReadBufferedUnsignedLongBigEndian(File,(unsigned long*)&FormChunkLength))
{
goto DiskErrorPoint1;
}
/* 4-byte type */
/* "AIFF" = AIFF format file */
/* "AIFC" = AIFF-C format file */
if (!ReadBufferedInput(File,4,CharBuff))
{
goto DiskErrorPoint1;
}
if (MemEqu(CharBuff,"AIFF",4))
{
IsAnAIFFCFile = False;
}
else if (MemEqu(CharBuff,"AIFC",4))
{
IsAnAIFFCFile = True;
}
else
{
UnknownAIFFFilePoint:
AlertHalt("The file is not a variant of AIFF or AIFF-C file that I can deal with.",NIL);
goto ExitTheProcedureErrorPoint;
}
FormChunkLength -= 4;
/* now, read in chunks until we die */
DiskErrorOccurred = False;
while (!DiskErrorOccurred && (FormChunkLength > 0))
{
long LocalChunkLength;
/* get the chunk type */
if (!ReadBufferedInput(File,4,CharBuff))
{
goto DiskErrorPoint1;
}
/* get the chunk length */
if (!ReadBufferedUnsignedLongBigEndian(File,(unsigned long*)&LocalChunkLength))
{
goto DiskErrorPoint1;
}
FormChunkLength -= 8;
/* adjust for even alignment */
if ((LocalChunkLength % 2) != 0)
{
LocalChunkLength += 1;
}
FormChunkLength -= LocalChunkLength;
/* decode the chunk */
if (MemEqu(CharBuff,"COMM",4))
{
unsigned long Exponent;
unsigned long Mantissa;
char StupidExtendedThang[10];
if (!IsAnAIFFCFile)
{
unsigned short ShortInteger;
/* Common Chunk for AIFF files */
/* "COMM" */
/* 4-byte big endian length. */
/* always 18 for AIFF files */
if (LocalChunkLength != 18)
{
goto UnknownAIFFFilePoint;
}
/* 2-byte big endian number of channels */
if (!ReadBufferedUnsignedShortBigEndian(File,&ShortInteger))
{
goto DiskErrorPoint1;
}
switch (ShortInteger)
{
default:
goto UnknownAIFFFilePoint;
case 1:
NumChannels = eSampleMono;
NumChannelsIsValid = True;
break;
case 2:
NumChannels = eSampleStereo;
NumChannelsIsValid = True;
break;
}
/* 4-byte big endian number of sample frames */
if (!ReadBufferedUnsignedLongBigEndian(File,&NumSampleFrames))
{
goto DiskErrorPoint1;
}
NumSampleFramesIsValid = True;
/* 2-byte big endian number of bits per sample */
/* a value in the domain 1..32 */
if (!ReadBufferedUnsignedShortBigEndian(File,&ShortInteger))
{
goto DiskErrorPoint1;
}
switch (ShortInteger)
{
default:
goto UnknownAIFFFilePoint;
case 1: case 2: case 3: case 4:
case 5: case 6: case 7: case 8:
NumBits = 8;
NumBitsIsValid = True;
break;
case 9: case 10: case 11: case 12:
case 13: case 14: case 15: case 16:
NumBits = 16;
NumBitsIsValid = True;
break;
}
/* 10-byte extended precision number of frames per second */
if (!ReadBufferedInput(File,10,StupidExtendedThang))
{
goto DiskErrorPoint1;
}
}
else
{
unsigned short ShortInteger;
long Dumper;
/* Common Chunk for AIFF-C files */
/* "COMM" */
/* 4-byte big endian length. */
/* always 18 for AIFF files */
if (LocalChunkLength < 22)
{
goto UnknownAIFFFilePoint;
}
/* 2-byte big endian number of channels */
if (!ReadBufferedUnsignedShortBigEndian(File,&ShortInteger))
{
goto DiskErrorPoint1;
}
switch (ShortInteger)
{
default:
goto UnknownAIFFFilePoint;
case 1:
NumChannels = eSampleMono;
NumChannelsIsValid = True;
break;
case 2:
NumChannels = eSampleStereo;
NumChannelsIsValid = True;
break;
}
/* 4-byte big endian number of sample frames */
if (!ReadBufferedUnsignedLongBigEndian(File,&NumSampleFrames))
{
goto DiskErrorPoint1;
}
NumSampleFramesIsValid = True;
/* 2-byte big endian number of bits per sample */
/* a value in the domain 1..32 */
if (!ReadBufferedUnsignedShortBigEndian(File,&ShortInteger))
{
goto DiskErrorPoint1;
}
switch (ShortInteger)
{
default:
goto UnknownAIFFFilePoint;
case 1: case 2: case 3: case 4:
case 5: case 6: case 7: case 8:
NumBits = 8;
NumBitsIsValid = True;
break;
case 9: case 10: case 11: case 12:
case 13: case 14: case 15: case 16:
NumBits = 16;
NumBitsIsValid = True;
break;
}
/* 10-byte extended precision number of frames per second */
if (!ReadBufferedInput(File,10,StupidExtendedThang))
{
goto DiskErrorPoint1;
}
/* 4-byte character code ID for the compression method */
/* "NONE" means there is no compression method used */
if (!ReadBufferedInput(File,4,CharBuff))
{
goto DiskErrorPoint1;
}
if (!MemEqu(CharBuff,"NONE",4))
{
goto UnknownAIFFFilePoint;
}
/* some characters in a string identifying the compression method */
/* this must be padded to an even number of bytes, but the pad is */
/* NOT included in the length descriptor for the chunk. */
/* for uncompressed data, the string should be */
/* "\x0enot compressed\x00", including the null, for 16 bytes. */
/* the total chunk length is thus 38 bytes. */
for (Dumper = 0; Dumper < LocalChunkLength - 22; Dumper += 1)
{
unsigned char Stupid;
if (!ReadBufferedUnsignedChar(File,&Stupid))
{
goto DiskErrorPoint1;
}
}
}
/* extended 22050 = 400D AC44000000000000 */
/* extended 22051 = 400D AC46000000000000 */
/* extended 44100 = 400E AC44000000000000 */
/* extended 44101 = 400E AC45000000000000 */
Exponent = (((long)StupidExtendedThang[0] & 0xff) << 8)
| ((long)StupidExtendedThang[1] & 0xff);
Mantissa = (((long)StupidExtendedThang[2] & 0xff) << 24)
| (((long)StupidExtendedThang[3] & 0xff) << 16)
| (((long)StupidExtendedThang[4] & 0xff) << 8)
| ((long)StupidExtendedThang[5] & 0xff);
SamplingRate = (Mantissa >> (0x401e - Exponent));
if (SamplingRate < MINSAMPLINGRATE)
{
SamplingRate = MINSAMPLINGRATE;
}
if (SamplingRate > MAXSAMPLINGRATE)
{
SamplingRate = MAXSAMPLINGRATE;
}
SamplingRateIsValid = True;
}
else if (MemEqu(CharBuff,"SSND",4))
{
unsigned long AlignmentFactor;
unsigned long OffsetToFirstByte;
MyBoolean OddNumberOfSoundBytes;
MyBoolean Cancelling = False;
/* Sound Data Chunk */
/* "SSND" */
/* 4-byte big endian number of bytes in sample data array */
/* 4-byte big endian offset to the first byte of sample data in the array */
if (!ReadBufferedUnsignedLongBigEndian(File,&OffsetToFirstByte))
{
goto DiskErrorPoint1;
}
if (OffsetToFirstByte != 0)
{
goto UnknownAIFFFilePoint;
}
/* 4-byte big endian number of bytes to which the sound data is aligned. */
if (!ReadBufferedUnsignedLongBigEndian(File,&AlignmentFactor))
{
goto DiskErrorPoint1;
}
if (AlignmentFactor != 0)
{
goto UnknownAIFFFilePoint;
}
/* any length vector of raw sound data. */
/* this must be padded to an even number of bytes, but the pad is */
/* NOT included in the length descriptor for the chunk. */
/* Samples are stored in an integral number of bytes, the smallest that */
/* is required for the specified number of bits. If this is not an even */
/* multiple of 8, then the data is shifted left and the low bits are zeroed */
/* Multichannel sound is interleaved with the left channel first. */
/* now, deal with the stuff we just got */
if (!NumBitsIsValid || !NumChannelsIsValid
|| !SamplingRateIsValid || !NumSampleFramesIsValid)
{
goto UnknownAIFFFilePoint;
}
LocalChunkLength -= 8; /* chop off alignment & offset from length */
OddNumberOfSoundBytes = ((LocalChunkLength & 1) != 0);
if (!OpenSoundChannel(SamplingRate,
(NumChannels == eSampleStereo) ? eStereo : eMono,
(NumBits == 8) ? e8bit : e16bit,
FRAMESPERBUFFER,NUMBEROFBUFFERS,INITIALNUMBUFFERS))
{
AlertHalt("Unable to open the sound output device.",NIL);
}
else
{
void* Buffer;
long UsedBufferFrames;
long FrameCount;
void* RawData;
Buffer = NIL;
FrameCount = 0;
switch (NumChannels)
{
default:
EXECUTE(PRERR(ForceAbort,"PlayAIFFFile: bad num channels"));
break;
case eSampleMono:
switch (NumBits)
{
default:
EXECUTE(PRERR(ForceAbort,"PlayAIFFFile: bad num bits"));
break;
case 8:
RawData = AllocPtrCanFail(FRAMESPERBUFFER,
"PlayAIFFFile Temp Buffer");
if (RawData == NIL)
{
AlertHalt("There is not enough memory available "
"to play the file.",NIL);
return;
}
while (LocalChunkLength > 0)
{
long BytesThisTime;
/* make sure there is a buffer to use */
while (Buffer == NIL)
{
Buffer = CheckOutSoundBuffer();
if (((Buffer != NIL)
&& RelinquishCPUJudiciouslyCheckCancel())
|| ((Buffer == NIL)
&& RelinquishCPUCheckCancel()))
{
Cancelling = True;
goto CancelPoint;
}
}
/* fill the buffer in with data */
if (LocalChunkLength < FRAMESPERBUFFER)
{
BytesThisTime = LocalChunkLength;
}
else
{
BytesThisTime = FRAMESPERBUFFER;
}
LocalChunkLength -= BytesThisTime;
PRNGCHK(RawData,RawData,BytesThisTime);
if (!ReadBufferedInput(File,BytesThisTime,(char*)RawData))
{
DiskErrorDuringPlaybackPoint:
AlertHalt("A disk error occurred during playback.",NIL);
DiskErrorOccurred = True;
Cancelling = True;
goto CancelPoint;
}
UsedBufferFrames = 0;
while (UsedBufferFrames < BytesThisTime)
{
PRNGCHK(Buffer,&(((signed char*)Buffer)[UsedBufferFrames]),
sizeof(((signed char*)Buffer)[UsedBufferFrames]));
((signed char*)Buffer)[UsedBufferFrames]
= ((signed char*)RawData)[UsedBufferFrames];
UsedBufferFrames += 1;
}
/* submit the buffer */
FrameCount += UsedBufferFrames;
UpdateSynthWindow(Window,SamplingRate,FrameCount,0,False);
SubmitBuffer((char*)Buffer,UsedBufferFrames,NIL,NIL);
Buffer = NIL;
}
break;
case 16:
RawData = AllocPtrCanFail(FRAMESPERBUFFER * 2,
"PlayAIFFFile Temp Buffer");
if (RawData == NIL)
{
AlertHalt("There is not enough memory available "
"to play the file.",NIL);
return;
}
while (LocalChunkLength > 0)
{
long BytesThisTime;
/* make sure there is a buffer to use */
while (Buffer == NIL)
{
Buffer = CheckOutSoundBuffer();
if (((Buffer != NIL)
&& RelinquishCPUJudiciouslyCheckCancel())
|| ((Buffer == NIL)
&& RelinquishCPUCheckCancel()))
{
Cancelling = True;
goto CancelPoint;
}
}
/* fill the buffer in with data */
if (LocalChunkLength < FRAMESPERBUFFER * 2)
{
BytesThisTime = LocalChunkLength;
}
else
{
BytesThisTime = FRAMESPERBUFFER * 2;
}
LocalChunkLength -= BytesThisTime;
PRNGCHK(RawData,RawData,BytesThisTime);
if (!ReadBufferedInput(File,BytesThisTime,(char*)RawData))
{
goto DiskErrorDuringPlaybackPoint;
}
UsedBufferFrames = 0;
while (UsedBufferFrames < BytesThisTime / 2)
{
PRNGCHK(Buffer,&(((signed short*)Buffer)[UsedBufferFrames]),
sizeof(((signed short*)Buffer)[UsedBufferFrames]));
((signed short*)Buffer)[UsedBufferFrames]
= (((short)((signed char*)RawData)[2 * UsedBufferFrames] & 0xff) << 8)
| (((signed char*)RawData)[2 * UsedBufferFrames + 1] & 0xff);
UsedBufferFrames += 1;
}
/* submit the buffer */
FrameCount += UsedBufferFrames;
UpdateSynthWindow(Window,SamplingRate,FrameCount,0,False);
SubmitBuffer((char*)Buffer,UsedBufferFrames,NIL,NIL);
Buffer = NIL;
}
break;
}
break;
case eSampleStereo:
switch (NumBits)
{
default:
EXECUTE(PRERR(ForceAbort,"PlayAIFFFile: bad num bits"));
break;
case 8:
RawData = AllocPtrCanFail(FRAMESPERBUFFER * 2,
"PlayAIFFFile Temp Buffer");
if (RawData == NIL)
{
AlertHalt("There is not enough memory available "
"to play the file.",NIL);
return;
}
while (LocalChunkLength > 0)
{
long BytesThisTime;
/* make sure there is a buffer to use */
while (Buffer == NIL)
{
Buffer = CheckOutSoundBuffer();
if (((Buffer != NIL)
&& RelinquishCPUJudiciouslyCheckCancel())
|| ((Buffer == NIL)
&& RelinquishCPUCheckCancel()))
{
Cancelling = True;
goto CancelPoint;
}
}
/* fill the buffer in with data */
if (LocalChunkLength < FRAMESPERBUFFER * 2)
{
BytesThisTime = LocalChunkLength;
}
else
{
BytesThisTime = FRAMESPERBUFFER * 2;
}
LocalChunkLength -= BytesThisTime;
PRNGCHK(RawData,RawData,BytesThisTime);
if (!ReadBufferedInput(File,BytesThisTime,(char*)RawData))
{
goto DiskErrorDuringPlaybackPoint;
}
UsedBufferFrames = 0;
while (UsedBufferFrames < BytesThisTime)
{
PRNGCHK(Buffer,&(((signed char*)Buffer)[UsedBufferFrames]),
sizeof(((signed char*)Buffer)[UsedBufferFrames]));
((signed char*)Buffer)[UsedBufferFrames]
= ((signed char*)RawData)[UsedBufferFrames];
UsedBufferFrames += 1;
}
/* submit the buffer */
FrameCount += UsedBufferFrames / 2;
UpdateSynthWindow(Window,SamplingRate,FrameCount,0,False);
SubmitBuffer((char*)Buffer,UsedBufferFrames / 2,NIL,NIL);
Buffer = NIL;
}
break;
case 16:
RawData = AllocPtrCanFail(FRAMESPERBUFFER * 4,
"PlayAIFFFile Temp Buffer");
if (RawData == NIL)
{
AlertHalt("There is not enough memory available "
"to play the file.",NIL);
return;
}
while (LocalChunkLength > 0)
{
long BytesThisTime;
/* make sure there is a buffer to use */
while (Buffer == NIL)
{
Buffer = CheckOutSoundBuffer();
if (((Buffer != NIL)
&& RelinquishCPUJudiciouslyCheckCancel())
|| ((Buffer == NIL)
&& RelinquishCPUCheckCancel()))
{
Cancelling = True;
goto CancelPoint;
}
}
/* fill the buffer in with data */
if (LocalChunkLength < FRAMESPERBUFFER * 4)
{
BytesThisTime = LocalChunkLength;
}
else
{
BytesThisTime = FRAMESPERBUFFER * 4;
}
LocalChunkLength -= BytesThisTime;
PRNGCHK(RawData,RawData,BytesThisTime);
if (!ReadBufferedInput(File,BytesThisTime,(char*)RawData))
{
goto DiskErrorDuringPlaybackPoint;
}
UsedBufferFrames = 0;
while (UsedBufferFrames < BytesThisTime / 2)
{
PRNGCHK(Buffer,&(((signed short*)Buffer)[UsedBufferFrames]),
sizeof(((signed short*)Buffer)[UsedBufferFrames]));
((signed short*)Buffer)[UsedBufferFrames]
= (((short)((signed char*)RawData)[2 * UsedBufferFrames] & 0xff) << 8)
| (((signed char*)RawData)[2 * UsedBufferFrames + 1] & 0xff);
UsedBufferFrames += 1;
}
/* submit the buffer */
FrameCount += UsedBufferFrames / 2;
UpdateSynthWindow(Window,SamplingRate,FrameCount,0,False);
SubmitBuffer((char*)Buffer,UsedBufferFrames / 2,NIL,NIL);
Buffer = NIL;
}
break;
}
break;
}
if (Buffer != NIL)
{
SubmitBuffer((char*)Buffer,UsedBufferFrames,NIL,NIL);
Buffer = NIL;
}
if (OddNumberOfSoundBytes)
{
unsigned char StupidChar;
if (!ReadBufferedUnsignedChar(File,&StupidChar))
{
DiskErrorOccurred = True;
}
}
CancelPoint: /* jump here to cancel playback */
ReleasePtr((char*)RawData);
if (Cancelling)
{
KillSoundChannel(); /* don't wait */
}
else
{
CloseSoundChannel(NIL,NIL);
}
}
}
else
{
/* just read the data & get rid of it */
while (LocalChunkLength > 0)
{
unsigned char Stupid;
if (!ReadBufferedUnsignedChar(File,&Stupid))
{
goto DiskErrorPoint1;
}
LocalChunkLength -= 1;
}
}
}
if (DiskErrorOccurred)
{
goto DiskErrorPoint1;
}
}
/* ask the user for a file, and then try to play back its contents */
void PlayAIFFFile(void)
{
FileSpec* WhereIsTheFile;
WhereIsTheFile = GetFileAny();
if (WhereIsTheFile != NIL)
{
PlayAIFFFileSpec(WhereIsTheFile);
DisposeFileSpec(WhereIsTheFile);
}
}
/* play specified AIFF file */
void PlayAIFFFileSpec(struct FileSpec* Where)
{
FileType* FileDesc;
CheckPtrExistence(Where);
if (OpenFile(Where,&FileDesc,eReadOnly))
{
BufferedInputRec* File;
File = NewBufferedInput(FileDesc);
if (File != NIL)
{
SynthWinRec* Window;
Window = NewSynthWindow(4,False/*don't show clipping*/);
if (Window != NIL)
{
PlayTheFile(File,Window);
DisposeSynthWindow(Window);
}
else
{
AlertHalt("There is not enough memory available to play the file.",NIL);
}
EndBufferedInput(File);
}
else
{
AlertHalt("There is not enough memory available to play the file.",NIL);
}
CloseFile(FileDesc);
}
else
{
AlertHalt("Unable to open the file for reading.",NIL);
}
}